package com.mkx.core.lock;

import com.mkx.core.monitor.RedisLockStats;
import com.mkx.core.wd.ThreadPoolWatchDog;
import com.mkx.core.WatchDog;
import com.mkx.core.ml.RedisLockMLPredictor;
import com.mkx.properties.RedisLockProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class RedisDistributedLock {
    
    private static final Logger logger = LoggerFactory.getLogger(RedisDistributedLock.class);
    
    // 类级别并发计数器，用于自适应重试间隔计算
    private static final AtomicInteger GLOBAL_LOCK_COUNT = new AtomicInteger(0);
    
    private final RedisTemplate<String, Object> redisTemplate;
    private final String lockKey;
    private final String lockValue;
    private final long expireTime;
    private final long retryInterval; // 基础重试间隔
    private final boolean adaptiveRetryEnabled; // 是否启用自适应重试
    private final WatchDog watchDog;
    private volatile boolean locked = false;
    private final AtomicInteger localRetryCount = new AtomicInteger(0); // 本地重试计数
    
    private static final String LOCK_SCRIPT = """
        -- 使用SET命令的NX和PX选项组合，确保原子性操作
        -- 这在Redis集群环境中更加可靠
        return redis.call('set', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2])
        """;
    
    private static final String UNLOCK_SCRIPT = """
        -- 使用Lua脚本确保释放锁的原子性检查和操作
        -- 增加对nil值的额外检查，提高集群环境下的鲁棒性
        local currentValue = redis.call('get', KEYS[1])
        if currentValue == ARGV[1] then
            -- 使用DEL命令删除锁
            return redis.call('del', KEYS[1])
        elseif currentValue == nil then
            -- 锁已不存在，返回1表示成功处理
            return 1
        else
            -- 锁被其他客户端持有，返回0表示失败
            return 0
        end
        """;
    
    public RedisDistributedLock(RedisTemplate<String, Object> redisTemplate, 
                               String lockKey,
                               long expireTime) {
        this(redisTemplate, lockKey, expireTime, 100); // 默认100ms重试间隔
    }
    
    public RedisDistributedLock(RedisTemplate<String, Object> redisTemplate, 
                               String lockKey,
                               long expireTime,
                               long retryInterval) {
        this(redisTemplate, lockKey, expireTime, retryInterval, true);
    }
    
    public RedisDistributedLock(RedisTemplate<String, Object> redisTemplate, 
                               String lockKey,
                               long expireTime,
                               long retryInterval,
                               boolean adaptiveRetryEnabled) {
        this.redisTemplate = redisTemplate;
        this.lockKey = lockKey;
        this.lockValue = UUID.randomUUID().toString() + ":" + Thread.currentThread().threadId();
        this.expireTime = expireTime;
        this.retryInterval = retryInterval;
        this.adaptiveRetryEnabled = adaptiveRetryEnabled;
        
        // 创建ThreadPoolWatchDog实例
        this.watchDog = new ThreadPoolWatchDog(redisTemplate, lockKey, lockValue, expireTime);
    }
    
    /**
     * 根据当前系统负载动态计算重试间隔
     * 高并发时增加间隔以减少系统压力，低并发时减小间隔以提高响应速度
     * 支持使用机器学习预测器进行智能优化
     */
    private long calculateAdaptiveRetryInterval() {
        if (!adaptiveRetryEnabled) {
            return retryInterval;
        }
        
        // 检查是否启用机器学习预测
        RedisLockMLPredictor mlPredictor = RedisLockMLPredictor.getInstance();
        if (mlPredictor != null) {
            try {
                int globalLockCount = GLOBAL_LOCK_COUNT.get();
                int currentRetryCount = localRetryCount.get();
                
                // 使用机器学习预测器计算重试间隔
                return mlPredictor.predictWaitTime(lockKey, retryInterval, currentRetryCount, globalLockCount);
            } catch (Exception e) {
                logger.warn("Error using ML predictor, falling back to default adaptive calculation", e);
                // 如果机器学习预测出错，回退到默认的自适应计算
            }
        }
        
        // 默认的自适应计算逻辑
        int globalLockCount = GLOBAL_LOCK_COUNT.get();
        int localRetries = localRetryCount.get();
        
        // 基础倍数计算公式：考虑全局锁数量和本地重试次数
        double multiplier = 1.0;
        
        // 全局锁数量影响：锁竞争激烈时增加间隔
        if (globalLockCount > 100) {
            multiplier = 2.0; // 高竞争场景
        } else if (globalLockCount > 50) {
            multiplier = 1.5; // 中等竞争场景
        }
        
        // 本地重试次数影响：连续失败时指数退避
        multiplier *= Math.min(1 + Math.log10(localRetries + 1), 3.0);
        
        // 计算最终间隔，确保有合理的上下限
        long calculatedInterval = (long) (retryInterval * multiplier);
        return Math.max(Math.min(calculatedInterval, retryInterval * 5), retryInterval / 2);
    }
    
    public RedisDistributedLock(RedisTemplate<String, Object> redisTemplate, 
                               RedisLockProperties properties,
                               String lockKey) {
        this(redisTemplate, lockKey, properties.getExpireTime(), properties.getRetryInterval());
    }
    
    public boolean tryLock(long waitTime, TimeUnit unit) throws InterruptedException {
        long start = System.currentTimeMillis();
        long timeout = unit.toMillis(waitTime);
        RedisLockStats stats = RedisLockStats.getInstance();
        
        // 记录竞争开始
        stats.recordContenderStart();
        
        try {
            while (System.currentTimeMillis() - start < timeout) {
                long acquireStartTime = System.currentTimeMillis();
                stats.recordAcquireStart();
                
                if (acquireLock()) {
                    long acquireTime = System.currentTimeMillis() - acquireStartTime;
                    stats.recordAcquireSuccess(acquireTime);
                    
                    // 记录成功获取锁的结果到机器学习预测器
                    try {
                        RedisLockMLPredictor mlPredictor = RedisLockMLPredictor.getInstance();
                        if (mlPredictor != null) {
                            mlPredictor.recordAttempt(lockKey, localRetryCount.get(), 0, true);
                        }
                    } catch (Exception e) {
                        logger.debug("Error recording success to ML predictor", e);
                    }
                    
                    localRetryCount.set(0); // 重置重试计数
                    return true;
                } else {
                    stats.recordAcquireFailed();
                    
                    // 记录失败获取锁的结果到机器学习预测器
                    try {
                        RedisLockMLPredictor mlPredictor = RedisLockMLPredictor.getInstance();
                        if (mlPredictor != null) {
                            long currentInterval = calculateAdaptiveRetryInterval();
                            mlPredictor.recordAttempt(lockKey, localRetryCount.get(), currentInterval, false);
                        }
                    } catch (Exception e) {
                        logger.debug("Error recording failure to ML predictor", e);
                    }
                    
                    // 计算下一次重试的等待时间
                    long waitTimeVal = calculateAdaptiveRetryInterval();
                    int currentRetryCount = localRetryCount.incrementAndGet();
                    
                    // 记录竞争事件
                    stats.recordCompetition(currentRetryCount, waitTimeVal);
                    
                    // 记录失败尝试到机器学习预测器
                    try {
                        RedisLockMLPredictor mlPredictor = RedisLockMLPredictor.getInstance();
                        if (mlPredictor != null) {
                            mlPredictor.recordAttempt(lockKey, currentRetryCount, waitTimeVal, false);
                        }
                    } catch (Exception e) {
                        logger.debug("Error recording failure to ML predictor", e);
                    }
                    
                    Thread.sleep(waitTimeVal);
                }
            }
            
            localRetryCount.set(0); // 超时后重置重试计数
            return false;
        } finally {
            // 记录竞争结束
            stats.recordContenderEnd();
        }
    }
    
    private boolean acquireLock() {
        try {
            // 增加全局锁计数器
            GLOBAL_LOCK_COUNT.incrementAndGet();
            
            // 修改脚本返回类型为Object，因为SET命令返回OK或nil
            DefaultRedisScript<Object> script = new DefaultRedisScript<>(LOCK_SCRIPT, Object.class);
            
            Object result = redisTemplate.execute(script, 
                Collections.singletonList(lockKey), 
                lockValue, 
                String.valueOf(expireTime));
            
            // 在Redis中，SET命令成功时返回OK字符串，失败时返回nil
            // 检查结果是否为"OK"字符串，确保在集群环境下正确识别锁获取成功
            if (result != null && "OK".equals(result.toString())) {
                locked = true;
                watchDog.start();
                logger.debug("Acquired lock: {} with WatchDog strategy: {}", 
                    lockKey, watchDog.getClass().getSimpleName());
                return true;
            }
        } catch (Exception e) {
            logger.error("Error acquiring lock: {}", lockKey, e);
        } finally {
            // 减少全局锁计数器
            GLOBAL_LOCK_COUNT.decrementAndGet();
        }
        return false;
    }
    
    public void unlock() {
        if (!locked) {
            return;
        }
        
        try {
            DefaultRedisScript<Long> script = new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class);
            
            Long result = redisTemplate.execute(script, 
                Collections.singletonList(lockKey), 
                lockValue);
            
            if (result != null && result == 1) {
                logger.debug("Released lock: {}", lockKey);
                localRetryCount.set(0); // 成功释放锁后重置重试计数
            } else {
                logger.warn("Failed to release lock: {}, maybe already released or expired", lockKey);
            }
        } catch (Exception e) {
            logger.error("Error releasing lock: {}", lockKey, e);
        } finally {
            watchDog.stop();
            locked = false;
            localRetryCount.set(0); // 确保无论如何都会重置本地重试计数
        }
    }
    
    public record LockInfo(String lockKey, String lockValue, boolean isLocked, String watchDogType) {}
    
    public LockInfo getLockInfo() {
        return new LockInfo(lockKey, lockValue, locked, watchDog.getClass().getSimpleName());
    }
    
    public boolean isLocked() { return locked; }
    public String getLockKey() { return lockKey; }
    public String getLockValue() { return lockValue; }
}